#region license
// Copyright (c) 2007 Rodrigo B. de Oliveira (rbo@acm.org)
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// 
//     * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//     * Neither the name of Rodrigo B. de Oliveira nor the names of its
//     contributors may be used to endorse or promote products derived from this
//     software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# Taken and modified from ConcurrentBoo

# This file is part of Grawlix.
# 
# Grawlix is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# 
# Grawlix is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public
# License along with Grawlix.  If not, see
# <http://www.gnu.org/licenses/>.
#endregion

"""Execute in a thread's message loop."""

namespace ConcurrentBoo
import ConcurrentBoo.Primitive
import System
import System.Collections.Generic as SCG

transient enum CacheExpirationPolicy:
"""Policy for cleaning out the cache."""
	
	ActiveExpiration
	"""
	The cache is purged by a background
	thread after the configured period.
	"""
	
	PassiveExpiration
	"""
	Items are kept in the cache and recomputed
	when requested after their expiration period.
	"""

transient class ComputationCache:
"""
A cache that computes requested values in the background as
they are needed.
"""
	
	def constructor():
	"""Construct a new cache assuming passive expiration."""
		self(timespan.FromMinutes(10))

	def constructor(expirationTimeout as timespan):
	"""
	Construct a new cache assuming passive expiration.
	Param expirationTimeout: The time until a value is considered expired.
	"""
		self(expirationTimeout, CacheExpirationPolicy.PassiveExpiration)
	
	def constructor(expirationTimeout as timespan,
	                expirationPolicy as CacheExpirationPolicy):
	"""
	Construct a new cache.
	Param expirationTimeout: The time until a value is considered expired.
	Param expirationPolicy: How new values are replaced.
	"""
		_expirationTimeout = expirationTimeout
		_expirationPolicy = expirationPolicy
		
	[lock]
	def Get(key, expression as Expression):
	"""
	Returns the cached computation if any or starts a new computation
	for the specified expression and returns immediately so multiple
	threads don't need to recompute the value.
	"""
		item as CacheItem
		if _cache.TryGetValue(key, item):
			if HasExpired(item) or \
				(item.Value.Finished and item.Value.Failure is not null):
				item.Value = Future(expression)
			return item.Value
			
		value = Future(expression)
		_cache.Add(key, CacheItem(Value: value))
		return value
		
	private def HasExpired(item as CacheItem):
	"""
	Check expiration on a cache item.
	Param item: The item in the cache.
	Returns: true if expired, else false.
	"""
		return (date.Now - item.LastUpdated) >= _expirationTimeout
	
	[getter(Cache, Protected: true)]
	private _cache = SCG.Dictionary[of object, CacheItem]()
	"""The internal cache."""
	
	[getter(ExpirationTimeout)]
	private _expirationTimeout as timespan
	"""The amount of time to pass before something is considered expired."""
	
	[getter(ExpirationPolicy)]
	private _expirationPolicy as CacheExpirationPolicy
	"""The expiration policy of the cache."""
	
	private transient class CacheItem:
	"""An item in the cache."""
		
		[getter(LastUpdated)]
		private _lastUpdated as date
		"""The last time this item was calculated."""
		
		private _value as Computation
		"""The calculated operation."""
		
		Value:
		"""The calculated operation."""
			get:
				return _value
			set:
				_lastUpdated = date.Now
				_value = value
				
		
